/*
 * Copyright (c) 2009 Robert Esser
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.e2ser.component;

import java.io.IOException;
import java.util.ArrayList;

import net.e2ser.petri.Color;
import net.e2ser.petri.Editor;
import net.e2ser.petri.RobPoint;
import net.e2ser.petri.SimulatorMode;
import net.e2ser.util.XMLWriter;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;

/**
 *
 * <p>Title: Petri Net Editor/Simulator</p>
 * <p>Description: A 2D node class</p>
 * <p>Copyright: Copyright (c) 2009</p>
 * <p>Company: </p>
 * @author Rob Esser
 * @version 1.0
 */
abstract public class AbstractComponent extends AbstractElement {
	
  private static final ArrayList<AbstractComponent> EMPTYLIST = new ArrayList<AbstractComponent>();
  private static final int DefaultComponentWidth = 20; 
  private static final int DefaultComponentHeight = 20; 
  private Rect componentBounds; 
  private float textSize = 6.0f;
 
  protected ShapeDrawable shape;
  protected String name = null; // the name of the element
  protected boolean inverse = false;
  public RobComponent parent = null;
  protected int fillColor = Color.GRAY;

  public AbstractComponent(RobComponent parent, int x, int y) {
    this.parent = parent;
    componentBounds = new Rect(x, y, x + DefaultComponentWidth, y + DefaultComponentHeight);
  }

  public AbstractComponent(RobComponent parent, int x, int y, int width, int height) {
    this.parent = parent;
    componentBounds = new Rect(x, y, x + width, y + height);
  }

  /**
   * return the current default width of the component
   * @return int
   */
  public static int DefaultComponentWidth() {
    return DefaultComponentWidth;
  }

  /**
   * return the current default height of the component
   * @return int
   */
  public static int DefaultComponentHeight() {
    return DefaultComponentHeight;
  }

  public Rect getComponentBounds() {
   return componentBounds;
  }

  public float textSize() {
    return textSize;
  }
  
  public void recalulateTextSize() {
    textSize = (float) (shape.getBounds().height() / 3.0);
    shape.getPaint().setTextSize(textSize);
  }

  public void resize(double scale) {
    Rect r = new Rect(componentBounds);
    r.left *= scale;
    r.right *= scale;
    r.top *= scale;
    r.bottom *= scale;
    shape.setBounds(r);
    recalulateTextSize();
   }

  /**
   * if fireable add myself to fireList
   * @param fireList Vector
   * @param mode SimulatorMode
   */
  abstract void addFireable(ArrayList<AbstractComponent> fireList, SimulatorMode mode);

  /**
   * notify my observers that my child comp has changed
   * @param comp AbstractComponent
   */
  public void changed(AbstractComponent comp) {
    parent.changed(comp);
  }

  /**
   * make myself noticeable
   * @param delay int
   */
  public void blink(int delay) {
    // force myself to blink
    inverse = true;
    parent.changed(this);
    if (delay > 0) {
      try {Thread.sleep(delay);
      } catch (InterruptedException e) {}
    } else {
      //invalid delay setting
    }
    inverse = false;
    parent.changed(this);
  }

  /**
   * add a token - default do nothing
   */
  public void addToken() {
  }

  /**
   * remove a token - default do nothing
   */
  public void removeToken() {
  }

  /**
   * fire the node - by default do nothing
   * @param mode SimulatorMode
   * @param animation boolean
   * @param delay int
   */
  public void fire(SimulatorMode mode, boolean animation, int delay) {
  }

  /**
   * draw myself on the Canvas
   * @param canvas where to draw
   */
  abstract public void draw(Canvas canvas);

  /**
   * Draw a marker to indicate that the node is selected
   * @param canvas where to draw
   */
  public void drawSelected(Canvas canvas) {
    Rect r = new Rect(shape.getBounds());
    shape.getPaint().setColor(Color.darker(Color.darker(fillColor)));
    r.inset(r.width() / 3, r.height() / 3);
    canvas.drawRect(r, shape.getPaint());
  }

  /**
   * return a point on my circumference to which an arc is joined
   * @param otherEnd RobPoint
   * @return RobPoint
   */
  abstract RobPoint connectionPoint(RobPoint otherEnd);

  /**
   * Return the top of this hierarchy
   * @return RobComponent
   */
  public RobComponent topNode() {
    // return the root node
    return parent.topNode();
  }

  /**
   * Can a conection from node be connected to me?
   * @param node AbstractComponent
   * @return boolean
   */
  public boolean canAddConnection(AbstractComponent node) {
    // can always start a connection from me
    return true;
  }

  /**
   * Only can add a connection if nodes are of different classes!
   * @param node1 AbstractComponent
   * @param node2 AbstractComponent
   * @return boolean
   */
  public boolean canAddConnection(AbstractComponent node1, AbstractComponent node2) {
    return !node1.getClass().equals(node2.getClass());
  }

  /**
   * Move the node origin
   * @param x double
   * @param y double
   */
  public void move(double x, double y) {
	  Rect r = shape.getBounds();
    int newX = Math.max((int) x, 0);
    int newY = Math.max((int) y, 0);
    r.offsetTo(newX, newY);
	  shape.setBounds(r);
	  componentBounds.offsetTo((int) (newX / Editor.DisplayScale), (int) (newY / Editor.DisplayScale));
  }

  /**
   * does my shape contain the point
   * @param x int
   * @param y int
   * @return boolean
   */
  public boolean contains(int x, int y) {
    return shape.getBounds().contains(x, y);
  }

  /**
   * the inside shape
   * @return Rectangle
   */
  public Rect bounds() {
    return shape.getBounds();
  }

  /**
   * Return the origin of my shape
   * @return RobPoint
   */
  public RobPoint origin() {
    return new RobPoint(shape.getBounds().left, shape.getBounds().top);
  }

  /**
   * Return the center of the shape
   * @return RobPoint
   */
  public RobPoint center() {
    return new RobPoint(shape.getBounds().centerX(), shape.getBounds().centerY());
  }


  /**
   * insert a new NodeInstance in parent
   * @param parent RobComponent
   * @param x int
   * @param y int
   * @return AbstractComponent
   */
  abstract public AbstractComponent insertElementIn(RobComponent parent, int x, int y, double scale);


  /**
   * return all input of ele
   * @param ele AbstractComponent
   * @return Vector
   */
  public ArrayList<AbstractComponent> allInputs(AbstractComponent ele) {
    return EMPTYLIST;
  }

  /**
   * return all output of ele
   * @param ele AbstractComponent
   * @return Vector
   */
  public ArrayList<AbstractComponent> allOutputs(AbstractComponent ele) {
    return EMPTYLIST;
  }

  /**
   * return all output of ele that are not also inputs
   * @param ele AbstractComponent
   * @return Vector
   */
  public ArrayList<AbstractComponent> onlyOutputs(AbstractComponent ele) {
	  return EMPTYLIST;
  }

  /**
   * return all inputs of myself
   * @return Vector
   */
  public ArrayList<AbstractComponent> allInputs() {
    return parent.allInputs(this);
  }

  /**
   * return all outputs of myself
   * @return Vector
   */
  public ArrayList<AbstractComponent> allOutputs() {
    return parent.allOutputs(this);
  }

  /**
   * return all outputs of myself that are not also inputs
   * @return Vector
   */
  public ArrayList<AbstractComponent> onlyOutputs() {
    return parent.onlyOutputs(this);
  }

  public int componentWidth() {
    return componentBounds.width();
  }

  public int componentHeight() {
    return componentBounds.height();
  }

  /**
   * Output an XML representation of myself
   * @param writer object that represents writer state
   * @throws IOException 
   */
  abstract public void toXML(XMLWriter writer) throws IOException;

}
